package pers.jl.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import pers.jl.common.MyException;
import pers.jl.dto.DishDto;
import pers.jl.entity.Category;
import pers.jl.entity.Dish;
import pers.jl.entity.DishFlavor;
import pers.jl.mapper.DishMapper;
import pers.jl.service.ICategoryService;
import pers.jl.service.IDishFlavorService;
import pers.jl.service.IDishService;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * <p>
 * 菜品管理 服务实现类
 * </p>
 *
 * @author luge
 * @since 2023-04-22
 */
@Service
@Slf4j
public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements IDishService {

    // 菜品不用注入，不然会报错，
    // 直接用super或者this就可以调用了，注入菜品口味接口，方便后续分别保存它们
    @Autowired
    private IDishFlavorService dishFlavorService;

    @Autowired
    @Lazy
    // 延迟互bean的加载，避免发生循环依赖，两个类相互引用对方，
    // 导致Spring在初始化bean的时候不知道先初始化哪个，从而形成循环依赖注入
    private ICategoryService categoryService;

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 由于涉及多表保存，基本的保存方法无法满足需求，这里我们自定义一个保存菜品和菜品口味的方法
     *
     * @param dishDto
     */
    @Override
    @Transactional// 涉及多表存取，添加事务安全一点
    public void saveWithFlavor(DishDto dishDto) {
        // 1.保存菜品，dishDto会把能赋上值的都赋值，因为dishDto是继承Dish的。
        this.save(dishDto);
        // 2.保存该菜品的每个口味，由于每个口味没有菜品id，
        // 存入数据库后不知道是哪个菜品的口味，所以需要全部添加菜品id
        // 获取菜品id
        Long id = dishDto.getId();
        // 给每个口味都添加菜品id，表示这是哪个菜品的口味
        List<DishFlavor> flavors = dishDto.getFlavors();
        flavors = flavors.stream().map((item) -> {// 使用stream()流映射的方式给每个口味对象添加菜品id
            item.setDishId(id);// 设置菜品id
            return item;// 映射完对象，返回每个口味对象
        }).collect(Collectors.toList());// 收集成集合，覆盖原来的口味集合

        // 批量存入数据库
        // 能执行到这，说明没有报错，可以以这个是否成功作为存入成功的依据
        dishFlavorService.saveBatch(flavors);
        // 保存完，清除对应菜品分类缓存，防止脏读
        String key = "dish_"+dishDto.getCategoryId()+"_1";
        redisTemplate.delete(key);
    }

    /**
     * 菜品管理页面分页查询
     *
     * @param page
     * @param pageSize
     * @param name
     * @return
     */
    @Override
    public Page<DishDto> pageForDish(int page, int pageSize, String name) {

        // 1. 构建Dish分页对象，在dish表中查出分页数据
        Page<Dish> dishPage = new Page<>(page, pageSize);
        // 如果菜品有名字，就模糊查询
        LambdaQueryWrapper<Dish> dishLambdaQueryWrapper = new LambdaQueryWrapper<>();
        dishLambdaQueryWrapper.like(name != null, Dish::getName, name);
        // 查出分页数据
        this.page(dishPage, dishLambdaQueryWrapper);

        // 2. 构建DishDto分页对象，拷贝Dish分页对象除records以外的所有分页数据，如total等。
        Page<DishDto> dishDtoPage = new Page<>();
        // 拷贝Dish分页对象除records以外的所有分页数据
        BeanUtils.copyProperties(dishPage, dishDtoPage, "redords");

        // 3. 对Dish分页对象的records数据进行处理，将所有Dish对象映射成DishDto对象
        List<Dish> dishRecords = dishPage.getRecords();
        List<DishDto> dishDtoRecords = dishRecords.stream().map((item) -> {
            DishDto dishDto = new DishDto();
            // 将每个dish对象的数据都拷贝到dishDto对象里
            BeanUtils.copyProperties(item, dishDto);
            // 获取每个dish对象的菜品分类id，根据菜品分类id查出菜品分类名称
            Long categoryId = item.getCategoryId();
            Category category = categoryService.getById(categoryId);
            // 有些数据不正确，导致菜品id无法对应菜品分类，防止报错，如果查不到菜品id所对应的菜品分类实体类，就不设置值
            if (category != null) {
                String categoryName = category.getName();
                // 每个DishDto对象都根据对应菜品分类id设置对应菜品分类名称
                dishDto.setCategoryName(categoryName);
            }
            // 返回DishDto对象
            return dishDto;
        }).collect(Collectors.toList());

        // 4. 将处理后的records重新赋值给DishDto的分页对象
        dishDtoPage.setRecords(dishDtoRecords);

        // 5. 返回DishDto的分页对象
        return dishDtoPage;
    }

    /**
     * 根据菜品id,同时查询菜品和菜品口味
     *
     * @param id
     * @return
     */
    @Override
    @Transactional
    public DishDto getByIdWithFlavor(Long id) {

        // 1. 根据菜品id查询菜品
        Dish dish = this.getById(id);

        // 2. 根据菜品id查询菜品口味
        LambdaQueryWrapper<DishFlavor> dishFlavorLambdaQueryWrapper = new LambdaQueryWrapper<>();
        // 筛选出口味表中，与传来菜品id相等的口味数据
        dishFlavorLambdaQueryWrapper.eq(DishFlavor::getDishId, id);
        // 查出口味集合
        List<DishFlavor> flavors = dishFlavorService.list(dishFlavorLambdaQueryWrapper);

        // 3. 将菜品和菜品口味数据，封装到DishDto对象中
        DishDto dishDto = new DishDto();
        // 先将dish的数据拷贝到dishDto
        BeanUtils.copyProperties(dish, dishDto);
        // 给dishDto设置口味
        dishDto.setFlavors(flavors);

        return dishDto;
    }

    /**
     * 同时更新菜品和菜品菜品口味
     *
     * @param dishDto
     * @return
     */
    @Override
    @Transactional
    public void updataWithFlavor(DishDto dishDto) {

        // 1. 更新dish表
        this.updateById(dishDto);

        // 2. 删除对应菜品在口味表的数据
        LambdaQueryWrapper<DishFlavor> dishFlavorLambdaQueryWrapper = new LambdaQueryWrapper<>();
        // 查出对应菜品在口味表的数据
        dishFlavorLambdaQueryWrapper.eq(DishFlavor::getDishId, dishDto.getId());
        // 删除口味数据
        dishFlavorService.remove(dishFlavorLambdaQueryWrapper);

        // 3. 重新插入对应菜品在口味表的数据
        List<DishFlavor> flavors = dishDto.getFlavors();
        // 对新口味数据进行处理，每个口味对象添加对应菜品id
        flavors = flavors.stream().map((item) -> {
            // 设置菜品id
            item.setDishId(dishDto.getId());
            return item;
        }).collect(Collectors.toList());
        // 重新插入
        dishFlavorService.saveBatch(flavors);
        // 更新完，清除对应菜品分类缓存，防止脏读
        String key = "dish_"+dishDto.getCategoryId()+"_1";
        redisTemplate.delete(key);
    }

    /**
     * 删除单个菜品或批量删除菜品
     * 使用逻辑删除
     *
     * @param ids
     * @return
     */
    @Override
    @Transactional
    public void deleteWithFlavor(String ids) {// 这个ids是字符串，有可能有多个id，以逗号分隔

        // 1. 判断ids中的菜品状态
        // 获取菜品id集合
        String[] idsList = ids.split(",");
        LambdaQueryWrapper<Dish> dishLambdaQueryWrapper = new LambdaQueryWrapper<>();
        dishLambdaQueryWrapper.in(Dish::getId, idsList);
        dishLambdaQueryWrapper.eq(Dish::getStatus, 1);
        // 查出菜品id在idsList中，且是启售状态的菜品数量
        int count = this.count(dishLambdaQueryWrapper);

        // 2. 启售状态不能删除，抛出异常
        if (count > 0) {
            // 含有启售菜品，无法删除，抛出异常
            throw new MyException("启售菜品无法删除！");
        } else {
            // 3. 停售状态可以删除
            // 删除菜品
            this.removeByIds(Arrays.asList(idsList));
            // 删除菜品口味
            LambdaQueryWrapper<DishFlavor> dishFlavorLambdaQueryWrapper = new LambdaQueryWrapper<>();
            dishFlavorLambdaQueryWrapper.in(DishFlavor::getDishId, Arrays.asList(idsList));
            dishFlavorService.remove(dishFlavorLambdaQueryWrapper);
        }
    }

    /**
     * 更改单个菜品状态或批量更改菜品状态
     *
     * @param status
     * @param ids
     */
    @Override
    public void updateStatus(int status, String ids) {

        // 1. 分割菜品id
        String[] idList = ids.split(",");

        // 2. 只有一个菜品id
        if (idList.length == 1) {
            // 根据菜品id查询菜品对象
            Dish dish = this.getById(ids);
            // 修改菜品对象状态
            dish.setStatus(status);
            // 修改状态
            this.updateById(dish);
            // 修改完状态，清除对应菜品分类缓存，防止脏读
            String key = "dish_"+dish.getCategoryId()+"_1";
            redisTemplate.delete(key);
        } else {
            // 3. 含有多个菜品id
            // 遍历菜品id.挨个修改状态
            for (String id : idList) {
                // 根据菜品id查询菜品对象
                Dish dish = this.getById(id);
                // 修改菜品对象状态
                dish.setStatus(status);
                // 修改状态
                this.updateById(dish);
                // 修改完状态，清除对应菜品分类缓存，防止脏读
                String key = "dish_"+dish.getCategoryId()+"_1";
                redisTemplate.delete(key);
            }
        }
    }

    /**
     * 根据分类id查出所有该菜品分类下的菜品，且状态为启售状态
     *
     * @param id
     * @return
     */
    @Override
    public List<DishDto> getDishByCategoryId(Long id) {

        // 1. 先去redis缓存中查，如果有直接返回，如果没有再执行SQL查询数据
        List<DishDto> dishDtoList = null;
        String key = "dish_"+id+"_1";
        dishDtoList = (List<DishDto>) redisTemplate.opsForValue().get(key);
        // 在缓存中查到数据了，直接返回
        if (dishDtoList != null) {
            return dishDtoList;
        }

        // 2. 根据分类id查出所有该菜品分类下的菜品，且状态为启售状态
        LambdaQueryWrapper<Dish> dishLambdaQueryWrapper = new LambdaQueryWrapper<>();
        // 要求菜品分类id相等才能匹配
        dishLambdaQueryWrapper.eq(id != null, Dish::getCategoryId, id);
        // 菜品必须启售状态
        dishLambdaQueryWrapper.eq(Dish::getStatus, 1);
        // 执行查询
        List<Dish> dishList = this.list(dishLambdaQueryWrapper);

        // 3. 对Dish集合进行处理，添加对应菜品的口味数据，封装成DishDto集合返回
        dishDtoList = dishList.stream().map((item) -> {
            DishDto dishDto = new DishDto();
            BeanUtils.copyProperties(item, dishDto);
            Long itemId = item.getId();
            LambdaQueryWrapper<DishFlavor> dishFlavorLambdaQueryWrapper = new LambdaQueryWrapper<>();
            dishFlavorLambdaQueryWrapper.eq(itemId!=null,DishFlavor::getDishId,itemId);
            // 根据菜品查出所有菜品口味信息
            List<DishFlavor> dishFlavors = dishFlavorService.list(dishFlavorLambdaQueryWrapper);
            // 设置到DishDto中
            dishDto.setFlavors(dishFlavors);
            return dishDto;
        }).collect(Collectors.toList());
        // 查到数据后，将数据存入缓存,有效期五分钟
        redisTemplate.opsForValue().set(key,dishDtoList,5, TimeUnit.MINUTES);
        // 返回DishDto结果
        return dishDtoList;
    }
}
